/* File name     :  nfivi_xmit.c
 * Author        :  Wentao Shang
 * 
 * Contents      :
 *    This file defines the core functions of the port cotrol algorithm. 
 *    The two functions in this file perform the port mapping and checksum
 *    update operations. They both use 'struct sk_buff *' as the only 
 *    parameter. They will be called by the netfiler hook functions defined
 *    in the 'nfivi' module. 'nfivi_xmit' module itself will call functions 
 *    defined in the 'nfivi_map' module. So this module will be installed 
 *    after 'nfivi_map' module and before 'nfivi' module.
 *
 */

#include "nfivi_xmit.h"

// Checksum update function, all parameters are in host byte order, while
// the return value is in network byte order
unsigned short checksumadjust( unsigned short chksum, unsigned short oldp, unsigned short newp )
{
	long x;
	unsigned short ret;
	
	x = chksum;
	x = ~x & 0xffff;
	x -= oldp & 0xffff;
	if( x <= 0 ) {
		x--;
		x &= 0xffff;
	}
	
	x += newp & 0xffff;
	if( x & 0x10000 ) {
		x++;
		x &= 0xffff;
	}
	
	x = ~x & 0xffff;
	ret = x / 256;
	ret += ( x & 0xff ) << 8;
	
	return ret;
}

/* port mapping functions called by nf hooks */

/* For inflow packets, change destination port*/
unsigned int port_map_in( struct sk_buff *skb )
{	
	struct ipv6hdr *iph = ipv6_hdr(skb);
	unsigned short newport;
	unsigned short csum;
	
	if( iph->nexthdr == IPPROTO_TCP ) {
		struct tcphdr *th = tcp_hdr(skb);
		
		/*
		printk("Inflow TCP packet!\n");
		*/
                
		if( iph->saddr.s6_addr[4] != 0xFF ) {
			printk("Non-IVI source!\n");
			return 1;
		}
		
		/*
		printk("Original Dest Port: %d\n", ntohs(th->dest));
		printk("Original TCP Checksum: %x\n", th->check);
		*/
		
		newport = get_inflow_map_port( ntohs(th->dest), &MapList );
		if( newport == 0 ) {
			printk("Find map failed! Drop packet!\n");
			return 0;
		}
		
		
		csum = checksumadjust( ntohs(th->check), ntohs(th->dest), newport );
		th->dest = htons(newport);
		th->check = csum;
		/*
		printk("New Dest Port: %d\n", ntohs(th->dest));
		printk("New Checksum: %x\n", csum);
		*/
	}
	else if( iph->nexthdr == IPPROTO_UDP ) {
		struct udphdr *uh = udp_hdr(skb);
		
		/*
		printk("Inflow UDP packet!\n");
		*/
                
		if( iph->saddr.s6_addr[4] != 0xFF ) {
			printk("Non-IVI source!\n");
			return 1;
		}
		
		/*
		printk("Original Dest Port: %d\n", ntohs(uh->dest));
		printk("Original UDP Checksum: %x\n", uh->check);
		*/
		
		newport = get_inflow_map_port( ntohs(uh->dest), &MapList );
		if( newport == 0 ) {
			// Should return NF_DROP here!
			printk("Find map failed! Drop packet!\n");
			return 0;
		}
		
		
		csum = checksumadjust( ntohs(uh->check), ntohs(uh->dest), newport );
		uh->dest = htons(newport);
		uh->check = csum;
		/*
		printk("New Dest Port: %d\n", ntohs(uh->dest));
		printk("New Checksum: %x\n", csum);
		*/
	}
	
	return 1;
}
EXPORT_SYMBOL(port_map_in);

/* For outflow packets, change source port*/
unsigned int port_map_out( struct sk_buff *skb )
{
	unsigned short tmpMod, tmpRes;
	struct ipv6hdr *iph = ipv6_hdr(skb);
	unsigned short newport;
	unsigned short csum;
	
	if( iph->nexthdr == IPPROTO_TCP ) {
		struct tcphdr *th = tcp_hdr(skb);
		
		/*
		printk("Outflow TCP packet!\n");
		*/
		
		if( iph->daddr.s6_addr[4] != 0xFF ) {
			printk("Non-IVI destination!\n");
			return 1;
		}
		
#ifdef IVIVER_NEW
// Use new ivi address format
		if( AutoConfig ) {
			tmpMod = (iph->saddr.s6_addr[9] & 0xF0 ) >> 4;
			tmpMod = 1 << tmpMod;
			tmpRes = ( (iph->saddr.s6_addr[9] & 0x0F ) << 8 ) + iph->saddr.s6_addr[10];
		
			//printk("tmpMod: %x %u, tmpRes: %x %u\n", tmpMod, tmpMod, tmpRes, tmpRes );
			
			if( Mod != tmpMod ) {
				Mod = tmpMod;
				printk("Automaticly change Mod to %d(0x%02x), free old maplist\n", tmpMod, tmpMod);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
			
			if( Res != tmpRes ) {
				Res = tmpRes;
				printk("Automaticly change Res to %d(0x%02x), free old maplist\n", tmpRes, tmpRes);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
		}
#else
// Use old ivi address format
		if( AutoConfig ) {
			tmpMod = ( iph->saddr.s6_addr[12] << 8 ) + iph->saddr.s6_addr[13];
			tmpRes = ( iph->saddr.s6_addr[14] << 8 ) + iph->saddr.s6_addr[15];
		
			//printk("tmpMod: %x %u, tmpRes: %x %u\n", tmpMod, tmpMod, tmpRes, tmpRes );
			
			if( Mod != tmpMod ) {
				Mod = tmpMod;
				printk("Automaticly change Mod to %d(0x%02x), free old maplist\n", tmpMod, tmpMod);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
			
			if( Res != tmpRes ) {
				Res = tmpRes;
				printk("Automaticly change Res to %d(0x%02x), free old maplist\n", tmpRes, tmpRes);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
		}
#endif
		
		/*
		printk("Original Source Port: %d\n", ntohs(th->source));
		printk("Original TCP Checksum: %x\n", th->check);
		*/
		
		newport = get_outflow_map_port( ntohs(th->source), &MapList );
		if( newport == 0 ) {
			printk("Find map failed! Drop packet!\n");
			return 0;
		}
		
		csum = checksumadjust( ntohs(th->check), ntohs(th->source), newport );
		th->source = htons(newport);
		th->check = csum;
		/*
		printk("New Source Port: %d\n", ntohs(th->source));
		printk("New Checksum: %x\n", csum);
		*/
	}
	else if( iph->nexthdr == IPPROTO_UDP ) {
		struct udphdr *uh = udp_hdr(skb);
		
		/*
		printk("Outflow UDP packet!\n");
		*/
	        
		if( iph->daddr.s6_addr[4] != 0xFF ) {
			printk("Non-IVI destination!\n");
			return 1;
		}
		
#ifdef IVIVER_NEW
// Use new ivi address format
		if( AutoConfig ) {
			tmpMod = (iph->saddr.s6_addr[9] & 0xF0 ) >> 4;
			tmpMod = 1 << tmpMod;
			tmpRes = ( (iph->saddr.s6_addr[9] & 0x0F ) << 8 ) + iph->saddr.s6_addr[10];
		
			//printk("tmpMod: %x %u, tmpRes: %x %u\n", tmpMod, tmpMod, tmpRes, tmpRes );
			
			if( Mod != tmpMod ) {
				Mod = tmpMod;
				printk("Automaticly change Mod to %d(0x%02x), free old maplist\n", tmpMod, tmpMod);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
			
			if( Res != tmpRes ) {
				Res = tmpRes;
				printk("Automaticly change Res to %d(0x%02x), free old maplist\n", tmpRes, tmpRes);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
		}
#else
// Use old ivi address format
		if( AutoConfig ) {
			tmpMod = ( iph->saddr.s6_addr[12] << 8 ) + iph->saddr.s6_addr[13];
			tmpRes = ( iph->saddr.s6_addr[14] << 8 ) + iph->saddr.s6_addr[15];
		
			//printk("tmpMod: %x %u, tmpRes: %x %u\n", tmpMod, tmpMod, tmpRes, tmpRes );
			
			if( Mod != tmpMod ) {
				Mod = tmpMod;
				printk("Automaticly change Mod to %d(0x%02x), free old maplist\n", tmpMod, tmpMod);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
			
			if( Res != tmpRes ) {
				Res = tmpRes;
				printk("Automaticly change Res to %d(0x%02x), free old maplist\n", tmpRes, tmpRes);
				free_map_list(&MapList);
				printk("free old maplist success!\n");
				Current = 0; //reset Current counter
			}
		}
#endif
		
		/*
		printk("Original Source Port: %d\n", ntohs(uh->source));
		printk("Original UDP Checksum: %x\n", uh->check);
		*/

		newport = get_outflow_map_port( ntohs(uh->source), &MapList );
		if( newport == 0 ) {
			printk("Find map failed! Drop packet!\n");
			return 0;
		}
		
		csum = checksumadjust( ntohs(uh->check), ntohs(uh->source), newport );
		uh->source = htons(newport);
		uh->check = csum;
		/*
		printk("New Source Port: %d\n", ntohs(uh->source));
		printk("New Checksum: %x\n", csum);
		*/
	}
	
	return 1;
}
EXPORT_SYMBOL(port_map_out);

MODULE_LICENSE("GPL");

